# coding=UTF-8
import json
import urllib

from common.utils_model import DjangoModelFieldParse, Null
from common.utils_model import RequestParamParse
from common.utils_view_extend import ViewStatus, OrderType, ExceptionParse
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core import serializers
from django.db.models.functions import Coalesce
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.generic import View, TemplateView


def cust_login_request(func):
    '''
    自定义ajax登陆验证装饰器，重定向状态码为302（303，307）
    :param func:
    :return:
    '''

    def wrapper(self, request, *args, **kwargs):
        if not request.session.is_empty():
            return func(self, request, *args, **kwargs)
        # 重定向url
        redirect = settings.LOGIN_URL
        return JsonResponse({
            'msg': '',
            'status': 302,
            'data': '',
            'redirect': redirect
        })

    return wrapper


class ModelApiView(View):
    '''
    请求结构说明：
    url例子：http://localhost/pickers/{picker_pk}? sort=a,b,c & order=asc & page=1 & size=100 & other_param=1...
    pickers：资源的复数

    请求类型说明：
        get：搜索资源
        post：增删改查资源

    请求参数（url和body）说明：
        使用键值对，值只接受字符串和数组，复杂数据请先序列化

    url过滤参数说明：
        picker_pk：资源主键，若存在，则搜索一条符合主键的资源，若不存在则搜索全部资源
        sort：排序字段，用英文 ',' 拼接，按照先后顺序进行排序
        order：排序类型，asc升序，desc降序，默认为升序
        page：分页的页码，默认不分页
        size：分页的资源数量
        other_param:代表某个需要过滤的参数，可拼接多个，只能进行简单的过滤
        csrfmiddlewaretoken：csrf攻击token
    请求的body内容说明：
        action：post请求处理函数后缀，处理函数命名例子：_action_find(),其中action指代find
        csrfmiddlewaretoken：csrf攻击token
        action_data：post中action处理函数所要处理的数据，可作为创建，更新资源的参数
        其他参数：除以上参数外的其他参数全部作为资源过滤使用，可进行复杂的过滤

    request其他处理说明：
        除以上信息在view中有明确字段存储，其他没有说明的数据全部存储在request_other_param

    请求处理逻辑：
        请求-->过滤url参数-->过滤body参数-->通过url和body参数处理出过滤的参数-->过滤-->排序-->action(查询操作-->分页)-->封装结果集

    _action_xx方法添加说明，若需要自定义查询数据，需要自己做分页处理

    response数据结果：
        {
            msg: 回馈消息,
            status: 状态码
            data:{ # 返回数据项
                data: 返回数据（可能无），
                total：可使用资源总数（可能无）
                ...
            }
        }
    '''
    # 可允许请求类型
    # http_method_names = ['get', 'post']
    # model字段

    # 默认action方法的前缀
    ACTION_PRE = '_action_'
    # 默认分页的每页数据量
    DEFAULT_PAGINATE_ORPHANS = 100
    # 默认数据传输的编码格式
    DEFAULT_ENCODE = 'utf-8'

    def __init__(self):
        super(View, self).__init__()
        self.queryset = {}
        self.fields_dict = {}
        self.fields_keys = []
        # url参数
        self.url_param_dict = {}
        # body参数
        self.body_param_dict = {}
        # sql过滤参数
        self.param_dict = {}
        # 其他request参数
        self.request_other_param = {}
        # 资源总数
        self.total = 0
        # 分页：每页数量
        self.paginate_orphans = self.DEFAULT_PAGINATE_ORPHANS
        # 分页：多少页
        self.paginate_by = None
        # 排序类型：升序asc，降序desc
        self.order = OrderType.ASC
        # 排序字段
        self.sort = []
        # 动作
        self.action = None
        # 请求处理时某一处理阶段的临时结果
        self.result = self.queryset

    # @cust_login_request
    def dispatch(self, request, *args, **kwargs):
        '''
        重写父类方法，添加异常处理
        :param request:
        :param args:
        :param kwargs:
         @override
        :return:
        '''
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        try:
            # 处理kwargs中的参数
            for key, value in kwargs.items():
                if key and value:
                    self.url_param_dict.update({
                        key: value
                    })
            # request预先处理，处理出参数
            self.request_preproccess(request)
            # 查询过滤
            self.query_filter(self.result)
            # 请求分发，处理业务逻辑
            result = handler(request, *args, **kwargs)
        except Exception as e:
            msg = self.exception_parse(e)
            data = e.__str__()
            result = self.get_response_result(data=data, msg=msg, status=ViewStatus.FAILURE)
        result = self.result_process(result)
        response = JsonResponse(result)
        return response

    def exception_parse(self, e):
        '''
        异常错误的解析，将异常错误解析成通用的提示语句
        :param e: 异常对象
        :return: 字符串类型的消息
        '''
        return ExceptionParse.parse(e)

    def result_process(self, result):
        '''
        业务逻辑处理完成后的返回结果再加工
        :param result:
        :return:
        '''
        return result

    def request_preproccess(self, request):
        '''
        request预处理
        从request中取出url的过滤参数，url排序，动作等参数，body中的过滤参数
        :param request:
        :return:
        '''
        # 过滤出请求参数
        if request.method == 'POST':
            # 获取request中body数据，并进行解码
            body_dict = self._parse_request_body(request.body)
            self.body_param_dict.update(body_dict)

        # 提取出其他request参数
        self.draw_request_other_param(request)

        # 提取出GET中的参数
        temp_param_dict = self.draw_request_param(request.GET)
        self.url_param_dict.update(temp_param_dict)

        # 提取POST中的参数
        temp_param_dict = self.draw_request_param(request.POST)
        self.body_param_dict.update(temp_param_dict)

        # 从body数据中获取action动作需要的数据以备后面action使用
        self.action_data = self.body_param_dict.get('action_data', '')
        if self.action_data:
            self.action_data = json.loads(s=self.action_data, encoding=self.DEFAULT_ENCODE)

        # 提取出通用的分页，排序字段
        self.draw_extra_params(self.url_param_dict)
        self.draw_extra_params(self.body_param_dict)

        # 取出url中的查询参数并存储到param_dict中
        self.draw_param_to_queryparam(self.url_param_dict)
        # 取出body的查询参数并存储到param_dict中
        self.draw_param_to_queryparam(self.body_param_dict)
        # 自定义一些非标准参数放到sql过滤字典中
        custom_dict = self.custom_body_deal()
        if isinstance(custom_dict, dict):
            self.param_dict.update(custom_dict)

    def deal_request_url(self, request_get):
        '''
        处理url的参数
        :param request_get:
        :return:
        '''
        temp_url_param_dict = {}
        temp_url_param_dict.update(request_get)
        for key, value in temp_url_param_dict.items():
            if value not in ['', None]:
                self.url_param_dict.update({
                    key: value[0]
                })

    def _parse_request_body(self, body):
        '''
        解析request中的body信息
        :param body: request对象中的body
        :return: 返回一个含有body数据的字典
        '''
        # body解码
        body = body.decode(self.DEFAULT_ENCODE)
        # body按照url格式解码
        body = urllib.parse.unquote(body)
        # 获取数据
        body_list = body.split('&')
        body_dict = {}
        for temp in body_list:
            temp_list = temp.split('=')
            key = temp_list[0]
            value = temp_list[1]
            temp = body_dict.get(key, None)
            temp_key_list = key.split('[]')
            temp_key_list_len = len(temp_key_list)
            if temp_key_list_len == 2 and temp_key_list[1] == '':
                key = key.replace('[]', '')
                if not temp:
                    body_dict.setdefault(key, [])
                body_dict[key].append(value)
            else:
                body_dict[key] = value

        return body_dict

    def draw_request_other_param(self, request):
        '''
        其他request过滤函数，子类可继承，从request中获取数据，存储到self.request_other_param字典中
        用于举例：获取request.FILES存储文件
        :param request:
        '''
        pass

    def draw_request_param(self, param):
        '''
        获取request.GET或request.POST中的数据
        :param param:request.GET或request.POST对象
        :return: 参数字典
        '''
        temp = {}
        for key in param.keys():
            key_list = key.split('[]')
            if len(key_list) == 2 and key_list[1] == '':
                value = param.getlist(key, [])
                key = key_list[0]
            else:
                value = param.get(key, '')
            temp.update({
                key: value
            })
        return temp

    def draw_param_to_queryparam(self, temp_param_dict):
        '''
        从url，body中提取必要的查询参数
        '''
        for key, value in temp_param_dict.items():
            if key and value not in ['', None]:
                self.param_dict.update({
                    key: value
                })

    def draw_extra_params(self, params):
        '''
        过滤出排序，csrf攻击的token，分页，及动作action参数
        :param params:
        '''
        # 过滤出page,size,csrfmiddlewaretoken,action
        page = params.get('page', '')
        if page is not None:
            if isinstance(page, str):
                self.paginate_by = int(page) if page.isdigit() else None
            if isinstance(page, int):
                self.paginate_by = page
                params.pop('page')

        size = params.get('size', '')
        if size is not None:
            if isinstance(size, str):
                self.paginate_orphans = int(
                    size) if size.isdigit() else self.paginate_orphans or self.DEFAULT_PAGINATE_ORPHANS
            if isinstance(size, int):
                self.paginate_orphans = size or self.paginate_orphans or self.DEFAULT_PAGINATE_ORPHANS
                params.pop('size')

        csrfmiddlewaretoken = params.get('csrfmiddlewaretoken', '')
        if csrfmiddlewaretoken:
            params.pop('csrfmiddlewaretoken')

        sort = params.get('sort', '')
        if sort:
            sort_list = sort.split(',')
            self.sort = sort_list
            params.pop('sort')

        order = params.get('order', '')
        self.order = order or OrderType.ASC
        if order:
            params.pop('order')

        # 过滤出动作
        action = params.get('action', '')
        if action:
            self.action = action
            params.pop('action')

    def custom_body_deal(self):
        '''
        获取自定义数据并存到sql过滤字典中,返回一个orm查询字典
        :return: 返回一个用于更新sql查询字典的django orm查询的字典
        '''
        pass

    def query_filter(self, result):
        '''
        orm 查询,排序
        :param result: 一个已处理的QuerySet，可进行后续查询过滤
        '''
        pass

    def get(self, request, *args, **kwargs):
        '''
        get请求：处理搜索model数据的请求
        :param request:
        :param id: url查询主键，默认选用mysql的id作为主键进行查询
        :param args:
        :param kwargs:
        :return: 返回封装的结果字典，结构请看类的说明中的response说明
        '''
        # 处理业务逻辑
        result = self.business_logic(request)
        return result

    def post(self, request, id, *args, **kwargs):
        '''
        post请求：处理增加，删除，修改model的请求
        :param request:
        :param id:  url查询主键，默认选用mysql的id作为主键进行查询
        :param args:
        :param kwargs:
        :return: 返回封装的结果字典，结构请看类的说明中的response说明
        '''
        # 处理业务逻辑
        result = self.business_logic(request)
        return result

    def business_logic(self, request):
        '''
        业务逻辑分发类，根据action的信息寻找处理函数，再将处理函数的结果封装返回
        action处理函数返回值说明：
            result: 处理完的最新信息（python基本类型数据dict，list，set，int，str...；不可为class对象）
            msg：处理结果信息
            status：处理完成后的状态，默认可使用0表示成功，1表示失败，也可选用restful的status类型进行说明
        :param request:
        :return: 调用self.get_response_result,返回一个封装的结果集
        '''
        result = None
        msg = ''
        status = ViewStatus.SUCCESS
        try:
            # action分发
            action = getattr(self, '%s%s' % (self.ACTION_PRE, self.action))
            temp = action(request)
            if not temp:
                pass
            elif len(temp) == 3:
                result, msg, status = temp[0], temp[1], temp[2]
            elif len(temp) == 2:
                msg, status = temp[0], temp[1]
            elif len(temp) == 1:
                status = temp[0]
            else:
                pass
        except Exception as e:
            # 找不到处理函数
            msg = self.exception_parse(e)
            status = ViewStatus.FAILURE
            result = e.__str__()
        result = self.get_response_result(data=result, msg=msg, status=status)
        return result

    def get_response_result(self, data=None, msg='', status=ViewStatus.SUCCESS):
        '''
        转化为统一的返回数据
        :param data:
        :param msg:
        :param status:
        :return:
        '''
        if data:
            data = {
                'data': data,
                'total': self.total
            }
        else:
            data = {}
        result = {
            'msg': msg,
            'status': status,
            'data': data
        }
        return result

    def _action_find(self, request):
        '''
        基础查找方法
        :param request:
        :return:
        '''
        msg = ''
        status = ViewStatus.SUCCESS
        data = None
        return data, msg, status

    def _action_test(self, request):
        '''
        基础测试方法
        :param request:
        :return:
        '''
        msg = '测试成功'
        status = ViewStatus.SUCCESS
        data = ''
        return data, msg, status


class DjangoModelApiView(ModelApiView, RequestParamParse, DjangoModelFieldParse):
    '''
    针对django自身model进行处理
    请求参数说明：
        支持符号：
                >=,<=,+,-
        举例：
            {'>=a': 1} => {'a__gte': 1}
    '''
    # 默认model中带有choices选项的字段，他的choices值的key的后缀
    MODEL_CHOICE_AFTER = '_choice'
    # 默认model序列化的格式
    SERIALIZE_FORMAT = 'json'

    def __init__(self):
        ModelApiView.__init__(self)
        # 获取默认可过滤参数字典
        if not self.fields_dict:
            _meta = self.model.__dict__['_meta']
            field_info_list = _meta.__dict__['_get_fields_cache'].values()
            field_list = list(field_info_list)[0]
            for field in field_list:
                key = field.__dict__['column']
                value = field
                if key and value:
                    self.fields_keys.append(key)
                    self.fields_dict.update({
                        key: value
                    })
        # queryset
        if not self.queryset:
            self.queryset = self.model.objects.all()
            self.result = self.queryset

        # 计算资源数据总量，并存储在total中
        self.total = self.queryset.count()

    def draw_param_to_queryparam(self, temp_param_dict):
        '''
        将url，body中的参数配置到sql过滤参数字典中
        '''
        # 在进行sql过滤之前进行类型转换
        temp_param_dict = self.parse_param_dict(temp_param_dict, self.fields_dict)

        # 合并参数
        self.param_dict.update(temp_param_dict)

    def query_filter(self, result):
        '''
        orm 查询
        :param result: 一个已处理的QuerySet，可进行后续查询过滤
        '''
        # sql过滤
        if self.param_dict:
            self.result = result.filter(**self.param_dict)
        self.query_custom_filter()
        # 排序
        if self.sort:
            sort_list = []
            for field in self.sort:
                if field in self.fields_keys:
                    sort_list.append(field)
            if sort_list:
                ordering = Coalesce(*tuple(sort_list))
                if self.order.upper() == OrderType.ASC.upper():
                    ordering = ordering.asc()
                else:
                    ordering = ordering.desc()
                self.result = self.result.order_by(ordering)

    def query_custom_filter(self):
        '''
        自定义通用查询集过滤
        :return:
        '''
        pass

    def clean_illegal(self, object_dict):
        '''
        清除非model中的字段数据
        :param object_dict:
        :return:
        '''
        if not object_dict:
            return None
        result = {}
        for key, value in object_dict.items():
            if key in self.fields_keys:
                result.update({
                    key: value
                })
        return result

    def serialize(self, queryset):
        '''
        默认提供django的序列化方法
        :param queryset: 查询集
        :return: json对象的list
        '''
        data = serializers.serialize(self.SERIALIZE_FORMAT, queryset)
        data = json.loads(data)
        data = [temp['fields'] for temp in data]
        return data

    def to_json(self, objects):
        '''
        自定义转json的方法
        将model对象转化成dict对象，并格式化其中的某些类型的数据：datetime，date等
        :param objects:
        :return:
        '''

        # 2.根据输出的类型信息进行序列化
        def model_to_json(object):
            '''
            将model数据转换成json
            :param object:
            :return:
            '''
            # 转换成json
            if isinstance(object, self.model):
                data = object.__dict__
                # 取出额外信息
                temp = {}
                for key in self.fields_keys:
                    value = data.get(key, Null)
                    if value is not Null:
                        temp.update({
                            key: value or ''
                        })
                data = temp

            # 取出choices数据
            data = self.model_choices_str(object, data)
            # 转换数据格式
            data = self.model_obj_format(data)
            return data

        if isinstance(objects, self.model):
            # 单个model对象
            data = model_to_json(objects) if objects else {}
        else:
            # 列表形式model
            data = []
            for obj in objects:
                if obj:
                    obj = model_to_json(obj)
                    if obj:
                        data.append(obj)
        return data

    def model_obj_format(self, object_json):
        '''
        转换数据格式
        :param object_json: model的json对象
        :return:
        '''
        result = {}
        for key, value in object_json.items():
            value = self.to_html_obj(type(value), value)
            result.update({
                key: value or ''
            })
        return result

    def model_choices_str(self, object, object_json):
        '''
        根据model中的类型，将带有choices选项的类型额外添加到返回的数据中
        :param object: model对象
        :param object_json: model的json对象
        :return:
        '''
        result = {}
        for field, val in object_json.items():
            field_type = self.fields_dict.get(field, '')
            if field_type and field_type.__dict__.get('choices', ''):
                val_str = getattr(object, 'get_%s_display' % field)()
                result.update({
                    field: val,
                    '%s%s' % (field, self.MODEL_CHOICE_AFTER): val_str
                })
            else:
                result.update({
                    field: val
                })
        return result

    def modelObj_extra_deal(self, modelObjs, deal_func):
        '''
        针对查询集进行额外处理
        :param modelObjs: 查询结果集
        :param deal_func: 处理函数
        :return:
        '''
        result = []
        for obj in modelObjs:
            temp = self.to_json(obj)
            temp = deal_func(obj, temp)
            result.append(temp)
        return result

    def _action_find(self, request):
        '''
        默认查询函数
        搜索model数据
        :return:
        '''
        # 分页
        status = ViewStatus.FAILURE
        if self.paginate_by is not None:
            start = self.paginate_by * self.paginate_orphans
            end = start + self.paginate_orphans
            self.result = self.result[start: end]
        data = self.to_json(self.result)
        msg = ''
        status = ViewStatus.SUCCESS
        return data, msg, status

    def _action_create(self, request):
        '''
        默认创建对象函数
        创建单个model数据
        :return:
        '''
        status = ViewStatus.FAILURE
        new_obj = None
        self.action_data = self.clean_illegal(self.action_data)
        if self.action_data:
            new_obj = self.model.objects.create(**self.action_data)
            new_obj = self.to_json(new_obj)
            msg = '创建成功'
            status = ViewStatus.SUCCESS
        return new_obj, msg, status

    def _action_bulk_create(self, request):
        '''
        默认批量创建函数
        批量创建model数据
        :return:
        '''
        status = ViewStatus.FAILURE
        result = None
        if self.action_data and isinstance(self.action_data, list):
            obj_list = []
            for temp in self.action_data:
                temp = self.clean_illegal(temp)
                obj_list.append(temp)
            result = self.model.objects.bulk_create(obj_list)
            msg = '批量创建成功'
            status = ViewStatus.SUCCESS
        return result, msg, status

    def _action_update(self, request):
        '''
        默认更新函数
        更新model数据
        :return:
        '''
        status = ViewStatus.FAILURE
        update_obj = None
        self.action_data = self.clean_illegal(self.action_data)
        if self.action_data:
            # 必须要有参数控制，否则容易导致全部数据被更新
            if self.param_dict:
                update_obj = self.result.update(**self.action_data)
                msg = '更新成功'
                status = ViewStatus.SUCCESS
            else:
                msg = '更新操作缺少过滤参数'
        return update_obj, msg, status

    def _action_delete(self, request):
        '''
        默认删除函数
        删除model数据
        :return:
        '''
        delete_obj, count = '', 0
        if self.param_dict:
            # 必须要有参数控制，避免错误操作引起问题
            count, delete_obj = self.result.delete()
            msg = '删除成功'
            status = ViewStatus.SUCCESS
        else:
            msg = '删除操作缺少过滤参数'
            status = ViewStatus.FAILURE
        self.total = self.total - count
        return self.total, msg, status


class CustTemplateView(TemplateView):
    '''
    自定义页面跳转
    '''
    # 配置登陆的重定向
    extra_context = {
        'LOGIN_URL': settings.LOGIN_URL
    }

    # @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super(TemplateView, self).dispatch(*args, **kwargs)
